﻿using System;
using Firefly.ExtensionMethods;
using Firefly.Framework.Input;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Firefly.Framework.Windows.Forms
{
    public class Textbox : Label
    {
        protected int _selectStart = 0;
        protected int _selectEnd = 0;
        private int _displayStart = 0;
        
        [ContentSerializer(Optional = true)]
        public Color ForeColorHighlight { get; set; }
        [ContentSerializer(Optional = true)]
        public Color BackColorHighlight { get; set; }
        [ContentSerializer(Optional = true)]
        public bool Locked { get; set; }
        [ContentSerializer(Optional = true)]
        public char PasswordCharacter { get; set; }
        [ContentSerializer(Optional = true)]
        public int SelectEnd
        {
            get { return _selectEnd; }
            set
            {
                _selectEnd = value;
                ScrollToCursor();
            }
        }
        [ContentSerializer(Optional = true)]
        public int SelectStart
        {
            get { return _selectStart; }
            set
            {
                _selectStart = value;
                ScrollToCursor();
            }
        }
        protected bool CursorBlink { get; set; }
        protected int CursorTime { get; set; }
        protected bool Selecting { get; set; }

        public Textbox()
        {
            BackColorHighlight = Color.Navy;
            ForeColorHighlight = Color.White;
            PasswordCharacter = char.MinValue;
            CursorBlink = true;
        }

        protected override void OnMouseDown(MouseButtonMap map, MouseButtonMapEventArgs e)
        {
            Focus();

            if (Text.Length > 0)
            {
                _selectEnd = _selectStart = WindowsService.Instance.Theme.Fonts[Font].CursorPosition(Text, InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint(), Interior, FontSize) + _displayStart;
                CursorTime = 500;
                Selecting = true;
            }
        }

        protected override void OnMouseUp(MouseButtonMap map, MouseButtonMapEventArgs e)
        {
            Selecting = false;
        }

        protected override void OnKeyDown(KeyMap map, KeyMapEventArgs e)
        {
            string newText = Text;

            switch (map.MappedButtons[0])
            {
                case Keys.Home:
                    _selectEnd = 0;
                    _displayStart = 0;

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                    {
                        _selectStart = _selectEnd;
                    }
                    break;
                case Keys.End:
                    _selectEnd = Text.Length;

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                    {
                        _selectStart = _selectEnd;
                    }
                    break;
                case Keys.Left:
                    _selectEnd -= 1;
                    _selectEnd = Math.Max(_selectEnd, 0);

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift)) 
                    { 
                        _selectStart = _selectEnd; 
                    }
                    break;
                case Keys.Right:
                    _selectEnd += 1;
                    _selectEnd = Math.Min(_selectEnd, Text.Length);

                    if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                        !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift)) 
                    { 
                        _selectStart = _selectEnd; 
                    }
                    break;
                case Keys.Back:
                    if (Locked)
                    {
                        break;
                    }

                    if (_selectEnd != _selectStart)
                    {
                        newText = Text.Remove(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
                        _selectStart = _selectEnd = Math.Min(_selectStart, _selectEnd);
                    }
                    else if (_selectStart > 0)
                    {
                        newText = Text.Remove(_selectStart - 1, 1);
                        _selectStart = _selectEnd -= 1;
                    }
                    break;
                case Keys.Delete:
                    if (Locked)
                    {
                        break;
                    }

                    if (_selectEnd != _selectStart)
                    {
                        newText = Text.Remove(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
                        _selectStart = _selectEnd = Math.Min(_selectStart, _selectEnd);
                    }
                    else
                    {
                        if (_selectStart < Text.Length)
                        { 
                            newText = Text.Remove(_selectStart, 1); 
                        }
                    }
                    break;
                default:
                    if (InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftControl) ||
                        InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightControl))
                    {
                        switch (map.MappedButtons[0])
                        {
                            case Keys.C:
                                if (PasswordCharacter > char.MinValue)
                                {
                                    break;
                                }

                                WindowsService.Instance.Clipboard.Text = this.SelectedText();
                                break;
                            case Keys.V:
                                if (Locked)
                                {
                                    break;
                                }

                                newText = Text.Remove(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
                                _selectStart = _selectEnd = Math.Min(_selectStart, _selectEnd);
                                string clipBoardText = WindowsService.Instance.Clipboard.Text;
                                newText = newText.Insert(_selectEnd, clipBoardText);
                                _selectStart = _selectEnd += clipBoardText.Length;
                                break;
                            case Keys.X:
                                if (PasswordCharacter > char.MinValue)
                                {
                                    break;
                                }

                                WindowsService.Instance.Clipboard.Text = SelectedText();

                                if (Locked)
                                {
                                    break;
                                }

                                newText = Text.Remove(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
                                _selectStart = _selectEnd = Math.Min(_selectStart, _selectEnd);
                                break;
                            case Keys.A:
                                _selectStart = 0;
                                _selectEnd = Text.Length;
                                break;
                        }
                    }
                    else
                    {
                        string key = string.Empty;

                        if (!InputService.Instance.KeyboardManager.IsButtonDown(Keys.LeftShift) &&
                            !InputService.Instance.KeyboardManager.IsButtonDown(Keys.RightShift))
                        {
                            key = KeyToStringConverter.Convert(map.MappedButtons[0], false);
                        }
                        else
                        {
                            key = KeyToStringConverter.Convert(map.MappedButtons[0], true);
                        }

                        if (key == "")
                        {
                            break;
                        }

                        newText = Text.Remove(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
                        _selectStart = _selectEnd = Math.Min(_selectStart, _selectEnd);
                        newText = newText.Insert(_selectEnd, key);
                        _selectStart = _selectEnd += 1;
                    }
                    break;
            }

            CursorTime = 500;
            ScrollToCursor();
            Text = newText;
            base.OnKeyDown(map, e);
        }

        private string DisplayText()
        {
            if (Text.Trim() == string.Empty)
            {
                return string.Empty;
            }

            string display = Text.Substring(_displayStart);
            return (PasswordCharacter > char.MinValue) ? new string(PasswordCharacter, display.Length) : display;
        }

        private string SelectedText()
        {
            return Text.Substring(Math.Min(_selectStart, _selectEnd), Math.Abs(_selectEnd - _selectStart));
        }

        private void ScrollToCursor()
        {
            while (_selectEnd < _displayStart)
            { 
                _displayStart -= 1; 
                _selectEnd += 1; 
                _selectStart += 1; 
            }
        }

        #region IPipelineSerializeable Members

        public override void DeserializeContent(ContentReader input)
        {
            base.DeserializeContent(input);
            BackColorHighlight = input.ReadColor();
            ForeColorHighlight = input.ReadColor();
            PasswordCharacter = input.ReadChar();
        }

        #endregion

        #region IDrawable Members

        public override void Draw(GameTime gameTime)
        {
            if (Visible)
            {
                Font font = WindowsService.Instance.Theme.Fonts[Font];
                int selectStart;
                int selectEnd;
                WindowsService.Instance.Theme.Textbox.Draw("Default", DrawRectangle, Color);
                string visibleText;
                float cursorPosition;
                visibleText = DisplayText();
                selectStart = Math.Min(Math.Max(_selectStart - _displayStart, 0), visibleText.Length);
                selectEnd = Math.Min(Math.Max(_selectEnd - _displayStart, 0), visibleText.Length);
                cursorPosition = font.CursorPosition(selectEnd, visibleText, FontSize);

                while (cursorPosition > Interior.Width && Interior.Width > 0)
                {
                    _displayStart += 1;
                    selectStart = Math.Max(_selectStart - _displayStart, 0);
                    selectEnd = Math.Max(_selectEnd - _displayStart, 0);
                    visibleText = DisplayText();
                    cursorPosition = font.CursorPosition(selectEnd, visibleText, FontSize);
                }

                if (HasFocus && (CursorBlink || CursorTime > 0))
                {
                    WindowsService.Instance.Theme.Fonts[Font].Draw(selectEnd, visibleText, Interior, FontColor, FontSize);
                }

                WindowsService.Instance.Theme.Fonts[Font].Draw(visibleText, Interior, FontSize, FontColor);

                if (selectEnd != selectStart)
                {
                    Vector2 startVector = font.MeasureString(FontSize, visibleText.Substring(0, selectStart));
                    Vector2 endVector = font.MeasureString(FontSize, visibleText.Substring(0, selectEnd));
                    string highlightText = visibleText.Substring(Math.Min(selectStart, selectEnd), Math.Abs(selectEnd - selectStart));
                    Rectangle highlightArea = new Rectangle(Interior.X + (int)Math.Ceiling(Math.Min(startVector.X, endVector.X)),
                        Interior.Y,
                        (int)Math.Ceiling(Math.Abs((endVector.X - startVector.X))),
                        (int)Math.Max(endVector.Y, startVector.Y));
                    highlightArea.Width -= Math.Max(highlightArea.Right - Interior.Right, 0);
                    WindowsService.Instance.DrawRectangle(highlightArea, BackColorHighlight);
                    highlightArea.Width += 1;
                    WindowsService.Instance.Theme.Fonts[Font].Draw(highlightText, highlightArea, FontSize, ForeColorHighlight);
                    DrawChildren(gameTime);
                }
            }
        }

        #endregion

        #region IUpdateable Members

        public override void Update(GameTime gameTime)
        {
            if (Enabled)
            {
                CursorBlink = ((gameTime.TotalRealTime.Milliseconds % 1000) >= 500);
                CursorTime = Math.Max(0, CursorTime - gameTime.ElapsedGameTime.Milliseconds);

                if (Selecting)
                {
                    _selectEnd = WindowsService.Instance.Theme.Fonts[Font].CursorPosition(Text, InputService.Instance.MouseManager.CurrentAxisPosition.ToPoint(), Interior, FontSize) + _displayStart;

                    if (_selectEnd < _displayStart)
                    {
                        _displayStart = _selectEnd;
                    }

                    while (_selectEnd < 0)
                    {
                        _selectEnd++;
                        _displayStart--;
                    }

                    _displayStart = Math.Max(_displayStart, 0);
                    _selectEnd = Math.Min(_selectEnd, Text.Length);
                    CursorTime = 500;
                }

                base.Update(gameTime);
            }
        }

        #endregion
    }
}
